# Static code analysis workflow for Sonar Cloud, results are published to projects:
#   - Windows: https://sonarcloud.io/project/overview?id=methane-powered-kit-windows
#   - Linux:   https://sonarcloud.io/project/overview?id=methane-powered-kit-linux
#   - MacOS:   https://sonarcloud.io/project/overview?id=methane-powered-kit-macos

name: '🎯 CI Sonar Scan'

on:
  push:
    branches: [ master, develop ]
    paths:
      - '.github/**/*sonar*.yml'
      - 'sonar-project.properties'
      - 'Apps/**'
      - 'Modules/**'
      - 'Tests/**'
      - 'Externals/**'
      - 'CMake/**'
      - 'Build/*/CI/**'
      - 'CMakeLists.txt'
      - 'CMakePresets.json'

  # "Pull request" event runs in context of the target repository branch, but remote repos do not have access to secrets,
  # it is used only for internal PRs from origin repository branches according to job condition below.
  pull_request:
    branches: [ master ]
    types: [opened, synchronize, reopened]
    paths:
      - '.github/**/*sonar*.yml'
      - 'sonar-project.properties'
      - 'Apps/**'
      - 'Modules/**'
      - 'Tests/**'
      - 'Externals/**'
      - 'CMake/**'
      - 'Build/*/CI/**'
      - 'CMakeLists.txt'
      - 'CMakePresets.json'

  # "Pull request target" event runs in context of the base repository and has access to secrets,
  # it is used only for external PRs from forked repositories to origin according to job condition below.
  pull_request_target:
    branches: [ master ]
    types: [ opened, synchronize, reopened ]
    paths:
      - 'Apps/**'
      - 'Modules/**'
      - 'Tests/**'
      - 'Externals/**'
      - 'CMake/**'
      - 'Build/*/CI/**'
      - 'CMakeLists.txt'
      - 'CMakePresets.json'

  schedule:
    - cron: '20 23 * * 3' # Scheduled workflow will not run in GitHub forks by default

env:
  METHANE_VERSION_MAJOR: 0
  METHANE_VERSION_MINOR: 7
  METHANE_VERSION_PATCH: 3
  METHANE_VERSION_BUILD: ${{ github.run_number }}

jobs:
  sonar_scan:
    name: ${{ matrix.name }}

    # Run sonar scan job only in context of the origin repository:
    # - Trigger on either "push" or "pull request" event for the origin repository owned branches
    # - Or trigger on "pull request target" event for external repositories to have access to secrets from origin repo context
    #   see https://github.com/orgs/community/discussions/26829
    if: ${{ github.repository == 'MethanePowered/MethaneKit' && (
               github.event_name == 'push' ||
              (github.event_name == 'pull_request' &&
               github.event.pull_request.head.repo.full_name == github.repository) ||
              (github.event_name == 'pull_request_target' &&
               github.event.pull_request.head.repo.full_name != github.repository)
            ) }}

    strategy:
      fail-fast: false
      matrix:
        include:
          - os: windows-latest
            name: "Win64_DX_SonarScan"
            named_logo: Windows
            config_preset: "Ninja-Win-DX-Scan"
            build_preset: "Ninja-Win-DX-Scan"
            sonar_project_key: "methane-powered-kit-windows"
            tests_coverage_reports: "Build/Output/Ninja-Win-DX-Scan/Install/Tests/Coverage/*.xml"

          - os: ubuntu-latest
            name: "Ubuntu_VK_SonarScan"
            named_logo: Linux
            config_preset: "Ninja-Lin-VK-Scan"
            build_preset: "Ninja-Lin-VK-Scan"
            sonar_project_key: "methane-powered-kit-linux"
            tests_coverage_reports: "Build/Output/Ninja-Lin-VK-Scan/Build/MethaneTestCoverage.info"

          - os: macos-13 # macos-latest is using M1 h/w, which is not supported by sonar scanner yet (see SonarSource/sonarcloud-github-c-cpp/pull/55)
            name: "MacOS_MTL_SonarScan"
            named_logo: Apple
            config_preset: "Ninja-Mac-MTL-Scan"
            build_preset: "Ninja-Mac-MTL-Scan"
            sonar_project_key: "methane-powered-kit-macos"
            tests_coverage_reports: "Build/Output/Ninja-Mac-MTL-Scan/Install/Tests/Coverage/*.lcov"

    runs-on: ${{ matrix.os }}

    env:
      BUILD_DIR: Build/Output/${{ matrix.config_preset }}/Build
      INSTALL_DIR: Build/Output/${{ matrix.config_preset }}/Install
      BUILD_LOG_FILE: Build/Output/${{ matrix.config_preset }}/Install/Build.log
      COVERAGE_LOG_FILE: Build/Output/${{ matrix.config_preset }}/Install/Coverage.log
      SCAN_LOG_FILE: Build/Output/${{ matrix.config_preset }}/Install/SonarScan.log
      COMPILE_COMMANDS_FILE: Build/Output/${{ matrix.config_preset }}/Build/compile_commands.json

    steps:
      - name: Checkout pull-request merge commit from origin
        if: ${{ github.event_name != 'pull_request_target' }}
        uses: actions/checkout@v3
        with:
          fetch-depth: 2 # 2 commits are necessary for CodeCov diff

      - name: Checkout pull-request merge commit from fork
        if: ${{ github.event_name == 'pull_request_target' }}
        uses: actions/checkout@v3
        with:
          ref: "${{ github.event.pull_request.merge_commit_sha }}"
          fetch-depth: 2 # 2 commits are necessary for CodeCov diff

      - name: Install Linux prerequisites
        if: ${{ runner.os == 'Linux' }}
        run: ./Build/Unix/CI/InstallLinuxPrerequisites.sh lcov

      - name: Install TestSpace
        if: ${{ github.event_name == 'push' }}
        uses: testspace-com/setup-testspace@v1
        with:
          domain: ${{ github.repository_owner }}

      # .NET Core is required by ReportGenerator
      - name: Install .NET Core
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 6.0.400

      - name: Install Sonar-Scanner
        uses: sonarsource/sonarcloud-github-c-cpp@v2

      # Ninja build is required to generate compile commands file used by Sonar-Scanner
      - name: Install Ninja
        uses: MethanePowered/gha-setup-ninja@master
        with:
          version: 1.11.1

      - name: Initialize Externals Cache
        uses: actions/cache@v3
        with:
          path: Build/Output/ExternalsCache
          key: ExternalsCache-${{ matrix.config_preset }}-${{ hashFiles('Externals/*.cmake') }}

      - name: Setup Developer Command Prompt for MSVC (VS2022 x64) to build with Ninja
        if: ${{ runner.os == 'Windows' }}
        uses: ilammy/msvc-dev-cmd@v1
        with:
          arch: x64

      - name: CMake Configure Preset ${{ matrix.config_preset }}
        shell: bash
        env:
          # Set BUILD version to zero to benefit from SonarCloud analysis cache (version change invalidates cache at each run)
          METHANE_VERSION_BUILD: 0
        run: |
          ./Build/Unix/CI/CMakeConfigurePreset.sh "${{ matrix.config_preset }}"
          if [ -f $COMPILE_COMMANDS_FILE ]; then
            cp "$COMPILE_COMMANDS_FILE" "$INSTALL_DIR"
          else
            echo "Compile commands file was not found!"
          fi

      - name: CMake Build Preset ${{ matrix.build_preset }}
        shell: bash
        run: ./Build/Unix/CI/CMakeBuildPreset.sh "${{ matrix.build_preset }}"

      - name: Download OpenCppCoverage
        if: ${{ runner.os == 'Windows' }}
        shell: powershell
        working-directory: 'Build/Output/${{ matrix.config_preset }}/Install/Tests'
        run: ${{ github.workspace }}\Build\Windows\CI\DownloadOpenCppCoverageRelease.ps1

      - name: Run all unit-tests with OpenCppCoverage code coverage on Windows
        if: ${{ runner.os == 'Windows' }}
        shell: cmd
        working-directory: 'Build\Output\${{ matrix.config_preset }}\Install\Tests'
        run: |
          ${{ github.workspace }}\Build\Windows\CI\RunUnitTestsWithCoverage.bat cobertura sonarqube junit ^
            "${{ github.workspace }}\Modules" ^
            Build/Output/${{ matrix.config_preset }}/Install/Tests

      - name: Run all unit-tests to collect SonarQube and JUnit test results on Linux
        if: ${{ runner.os == 'Linux' }}
        working-directory: 'Build/Output/${{ matrix.config_preset }}/Install/Tests'
        run: ${{ github.workspace }}/Build/Unix/CI/RunUnitTests.sh sonarqube junit

      - name: Run all unit-tests with code coverage using CTest and GCov on Linux
        if: ${{ runner.os == 'Linux' && (success() || failure()) }}
        run: ./Build/Unix/CI/CMakeBuildPreset.sh "${{ matrix.build_preset }}" MethaneTestCoverage "$COVERAGE_LOG_FILE"

      - name: Run all unit-tests with LCov code coverage on MacOS
        if: ${{ runner.os == 'macOS' }}
        working-directory: 'Build/Output/${{ matrix.config_preset }}/Install/Tests'
        run: ${{ github.workspace }}/Build/Unix/CI/RunUnitTestsWithCoverage.sh lcov sonarqube junit

      - name: Generate Code Coverage Reports
        if: ${{ success() || failure() }}
        uses: danielpalme/ReportGenerator-GitHub-Action@5.1.13
        with:
          reports: ${{ matrix.tests_coverage_reports }}
          targetdir: 'Build/Output/${{ matrix.config_preset }}/Install/Tests/Coverage/Report'
          reporttypes: 'Cobertura;SonarQube'
          title: 'Methane Tests Code Coverage for ${{ matrix.build_preset }}'
          tag: '${{ env.METHANE_VERSION_MAJOR }}.${{ env.METHANE_VERSION_MINOR }}.${{ env.METHANE_VERSION_PATCH }}.${{ env.METHANE_VERSION_BUILD }}'

      - name: Upload Code Coverage Cobertura Report
        if: ${{ success() || failure() }}
        uses: actions/upload-artifact@v3
        with:
          name: MethaneKit_${{ matrix.name }}_CoverageResults
          path: Build/Output/${{ matrix.config_preset }}/Install/Tests/Coverage/Report/Cobertura.xml

      - name: Upload Build Log and Code Coverage to TestSpace server
        if: ${{ github.event_name == 'push' && (success() || failure()) }}
        continue-on-error: true # testspace will fail if test result already exists for current build id, but it's OK on build re-run.
        shell: bash
        run: |
          testspace \
            "[ ${{ matrix.name }} ]Build/Output/${{ matrix.config_preset }}/Install/Tests/Coverage/Report/Cobertura.xml" \
            "[ ${{ matrix.name }} ]${{ env.BUILD_LOG_FILE }}"

      - name: Publish Code Coverage to CodeCov server
        if: ${{ github.event_name != 'pull_request_target' && (success() || failure()) }}
        uses: codecov/codecov-action@v3
        with:
          files: Build/Output/${{ matrix.config_preset }}/Install/Tests/Coverage/Report/Cobertura.xml
          flags: unittests,${{ runner.os }}
          name: ${{ matrix.name }}

      - name: Publish Code Coverage to CodeCov server (from PR target)
        if: ${{ github.event_name == 'pull_request_target' && (success() || failure()) }}
        uses: codecov/codecov-action@v3
        with:
          override_pr: ${{ github.event.pull_request.number }}
          override_commit: ${{ github.event.pull_request.head.sha }}
          files: Build/Output/${{ matrix.config_preset }}/Install/Tests/Coverage/Report/Cobertura.xml
          flags: unittests,${{ runner.os }}
          name: ${{ matrix.name }}

      - name: Publish Test Results to GitHub
        if: ${{ success() || failure() }}
        uses: EnricoMi/publish-unit-test-result-action/composite@v2
        with:
          check_name: ${{ matrix.name }} Test Results
          comment_title: ${{ matrix.name }} Test Results
          files: Build/Output/${{ matrix.config_preset }}/Install/Tests/Results/junit/*Test.xml
          action_fail: true
          action_fail_on_inconclusive: true

      - name: Run Sonar Scanner
        if: ${{ success() || failure() }}
        shell: bash
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_ORGANIZATION: methane-powered
          GITHUB_COMMIT_SHA: ${{ github.event_name == 'push' && github.sha || github.event.pull_request.head.sha }}
          GITHUB_PR_FLAG: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && 1 || 0 }}
          GITHUB_PR_REPO: ${{ github.repository }}
          GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
          GITHUB_PR_BRANCH: ${{ github.event.pull_request.head.ref }}
          GITHUB_PR_BASE: ${{ github.event.pull_request.base.ref }}
        run: |
            ./Build/Unix/CI/RunSonarScanner.sh "${{ matrix.sonar_project_key }}" \
              "Build/Output/${{ matrix.config_preset }}/Build" \
              "Build/Output/${{ matrix.config_preset }}/Install/Tests"

      - name: Archive Scan Artifacts
        if: ${{ success() || failure() }}
        shell: bash
        working-directory: Build/Output/${{ matrix.config_preset }}/Install
        run: 7z a -t7z -mx=9 MethaneKit_${{ matrix.name }}.7z *

      - name: Upload Archived Scan Artifacts
        if: ${{ success() || failure() }}
        uses: actions/upload-artifact@v3
        with:
          name: MethaneKit_${{ matrix.name }}_${{ env.METHANE_VERSION_MAJOR }}.${{ env.METHANE_VERSION_MINOR }}.${{ env.METHANE_VERSION_PATCH }}.${{ env.METHANE_VERSION_BUILD }}
          path: Build/Output/${{ matrix.config_preset }}/Install/MethaneKit_${{ matrix.name }}.7z

      - name: Update Badge Parameters
        if: ${{ (github.event_name == 'push' || github.event_name == 'schedule') && always() }}
        shell: bash
        run: ./Build/Unix/CI/UpdateBadgeParameters.sh "${{ job.status }}"

      - name: Update Badge JSON
        if: ${{ (github.event_name == 'push' || github.event_name == 'schedule') && always() }}
        uses: schneegans/dynamic-badges-action@v1.6.0
        with:
          auth: ${{ secrets.GIST_TOKEN }}
          gistID: 96d788046ccd52b45b3354a99f8569c3
          filename: MethaneKit_${{ matrix.name }}_${{ github.ref_name }}.json
          namedLogo: ${{ matrix.named_logo }} # https://simpleicons.org
          label: ${{ matrix.name }}
          labelColor: #f5f5f5
          logoColor: #f5f5f5
          message: ${{ env.badge_message }}
          color: ${{ env.badge_color }}
